/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
#include "multidata.h"

namespace gimi {
  
int writeQueueIdentifiersToRedirections(t_redirectionList &redirections,
                                        const t_queueIdentifiers &queues,
                                        const int majorTypeId,
                                        const int minorTypeId,
                                        const int senderId)
{
  int n = 0;
  
  // Write queues
  for (t_queueIdentifiersConstIterator qIter = queues.begin();
       qIter != queues.end();
       ++qIter)
  {
    redirections.push_back(GIMIRedirection(*qIter, majorTypeId, minorTypeId, senderId));
    ++n;
  }

  return n;
}

SenderInfo::SenderInfo(const std::string &sName)
  : senderName (sName),
    queues ()
{
}

MinorInfo::MinorInfo()
  : senderInformation (),
    queues ()
{
}

MajorInfo::MajorInfo()
  : minorInformation (),
    queues ()
{
}

int writeSenderQueuesToRedirections(t_redirectionList &redirections,
                                    const t_senderInformation &senders,
                                    const int majorTypeId,
                                    const int minorTypeId,
                                    const int senderId)
{
  int n = 0;
  
  if (senderId < 0) { // Sender not defined.
    // Loop through senders.
    for (t_senderInformationConstIterator senderIter = senders.begin();
         senderIter != senders.end();
         ++senderIter)
    {
      n += writeQueueIdentifiersToRedirections(redirections,
                                               senderIter->second.queues,
                                               majorTypeId,
                                               minorTypeId,
                                               senderIter->first);
    }
  } else { // Senderid defined.
    // Find sender.
    t_senderInformationConstIterator senderIter = senders.find(senderId);
    if (senderIter != senders.end()) { // Sender found
      n += writeQueueIdentifiersToRedirections(redirections,
                                               senderIter->second.queues,
                                               majorTypeId,
                                               minorTypeId,
                                               senderIter->first);
    } else { // Sender not found.
      //dPrint(15, "Did not find sender 0x%08x in %d:%d.", senderId, majorTypeId,
      //      minorTypeId);
    }
  }
  
  return n;
}

int writeMinorQueuesToRedirections(t_redirectionList &redirections,
                                   const t_minorRedirectMap &minorTypes,
                                   const bool exactTypes,
                                   const int majorTypeId,
                                   const int minorTypeId,
                                   const int senderId)
{
  int n = 0;
  if (minorTypeId < 0) { // Minortype not defined.
    if (!exactTypes) { // Loop only if exactTypes not defined.
            // Loop through minortypes.
      for (t_minorRedirectMapConstIterator minorIter = minorTypes.begin();
           minorIter != minorTypes.end();
           ++minorIter)
      {
        // Get local queues.
        n += writeQueueIdentifiersToRedirections(redirections,
                                                 minorIter->second.queues,
                                                 majorTypeId,
                                                 minorIter->first);

        // Get queues from sender(s)
        n += writeSenderQueuesToRedirections(redirections,
                                             minorIter->second.senderInformation,
                                             majorTypeId,
                                             minorIter->first,
                                             senderId);
      }
    }
  } else { // Minortype defined.
    t_minorRedirectMapConstIterator minorIter = minorTypes.find(minorTypeId);
    if (minorIter != minorTypes.end()) { // Minortype found.
      // Get local queues.
      n += writeQueueIdentifiersToRedirections(redirections,
                                               minorIter->second.queues,
                                               majorTypeId,
                                               minorIter->first);

      if (!exactTypes || (exactTypes && senderId > -1)) {
        // Get queues from sender(s)
        n += writeSenderQueuesToRedirections(redirections,
                                             minorIter->second.senderInformation,
                                             majorTypeId,
                                             minorIter->first,
                                             senderId);
      }
    } else { // Minortype not found.
      //dPrint(15, "Did not find minorType %d in majorType %d", minorTypeId,
      //       majorTypeId);
    }
  }
  return n;
}

MultiData::MultiData(Connector *connPtr)
  : reservedQueues (),
    nextQueueNumber (1),
    redirectInformation (),
    redirectMutex (ownMutex_Init()),
    connectionPtr(connPtr)
{
  assert(redirectMutex != NULL);
  assert(connectionPtr != NULL);
  reservedQueues.insert(0); // Reserve default queue 0 so it's id won't be
                            // handed out as an available option.
}

MultiData::~MultiData()
{
  ownMutex_Destroy(redirectMutex);
  // Connector pointer should not be destroyed here!
}

unsigned int MultiData::reserveQueue()
{
  unsigned int queueNumber = 0;
  bool insertResult = false;
  
  ownMutex_Lock(redirectMutex);
  unsigned int startPoint = nextQueueNumber;
  do {
    insertResult = (reservedQueues.insert(nextQueueNumber)).second;
    ++nextQueueNumber;
  } while (insertResult == false && startPoint != nextQueueNumber);
  
  if (startPoint == nextQueueNumber) { // Looped through and found no free values, not good.
    dPrint(ODERROR, "Could not reserve a queue because all numbers have been taken!! "
           "Either a serious bug has occurred or you have seriously abused redirections "
           "without freeing used queues.");
    queueNumber = 0;
  } else {
    queueNumber = nextQueueNumber - 1;
    //dPrint(30, "Reserved queue number %u.", queueNumber);
  }
  ownMutex_Unlock(redirectMutex);
  return queueNumber;
}
    
bool MultiData::releaseQueue(const unsigned int queueIdentifier)
{
  bool result = false;
  ownMutex_Lock(redirectMutex);
  if (reservedQueues.erase(queueIdentifier) > 0) {
    //dPrint(30, "Released queue number %u, cleaning up now.");
    int n = 0;
    if (!redirectInformation.empty()) {
      for (t_redirectMapIterator majorIter = redirectInformation.begin();
           majorIter != redirectInformation.end();
           ++majorIter)
      {
        n += majorIter->second.queues.erase(queueIdentifier);
        for (t_minorRedirectMapIterator minorIter = majorIter->second.minorInformation.begin();
             minorIter != majorIter->second.minorInformation.end();
             ++minorIter)
        {
          n += minorIter->second.queues.erase(queueIdentifier);
          for (t_senderInformationIterator senderIter = minorIter->second.senderInformation.begin();
               senderIter != minorIter->second.senderInformation.end();
               ++senderIter)
          {
            n += senderIter->second.queues.erase(queueIdentifier);
          }
        }
      }
    }
    //dPrint(30, "Removed %d redirections to queue %u", n, queueIdentifier);
    result = true;
  } else {
    dPrint(ODWARN, "Did not find queue %u to release.", queueIdentifier);
    result = false;
  }
  ownMutex_Unlock(redirectMutex);
  return result;
}

bool MultiData::addRedirection(const unsigned int queueIdentifier,
                               const int majorTypeId,
                               const int minorTypeId /* = -1 */,
                               const std::string &senderName /* = "" */,
                               const int senderId /* = -1 */)
{
  //  dPrint(25, "Called with %u, %d:%d, '%s', 0x%08x", queueIdentifier,
  //     majorTypeId, minorTypeId, senderName.c_str(), senderId);
  bool result = false;
  
  ownMutex_Lock(redirectMutex);
  t_redirectMapIterator majorIter = redirectInformation.find(majorTypeId);
  if (majorIter == redirectInformation.end()) {
    // Majortype not found, insert and get iterator.
    majorIter = (redirectInformation.insert(t_redirectMapPair(majorTypeId, MajorInfo()))).first;
    //dPrint(25, "Added new majortype %d", majorTypeId);
  }
  if (minorTypeId > -1) { // Insert redirection to specific minortype.
    t_minorRedirectMapIterator minorIter = majorIter->second.minorInformation.find(minorTypeId);
    if (minorIter == majorIter->second.minorInformation.end()) {
      // Minortype not found, insert and get iterator.
      minorIter = (majorIter->second.minorInformation.insert(
          t_minorRedirectMapPair(minorTypeId, MinorInfo()))).first;
      //dPrint(25, "Added new minortype %d in majortype %d", minorTypeId,
      //       majorTypeId);
    }
    if (!senderName.empty()) {
      if (senderId < 1) {
        dPrint(1, "Sendername given but senderId was invalid!");
        return false;
      }
      t_senderInformationIterator senderIter = minorIter->second.senderInformation.find(senderId);
      if (senderIter == minorIter->second.senderInformation.end()) {
        // Senderid not found, insert and get iterator
        senderIter = (minorIter->second.senderInformation.insert(
            t_senderInformationPair(senderId, SenderInfo(senderName)))).first;
        // dPrint(25, "Added new sender '%s' in %d:%d", senderName.c_str(),
        //       majorTypeId, minorTypeId);
      }
      result = (senderIter->second.queues.insert(queueIdentifier)).second;
      //dPrint(20, "Added redirection to queue %u for %d:%d from 0x%08x",
      //       queueIdentifier, majorTypeId, minorTypeId, senderId);
    } else {
      result = (minorIter->second.queues.insert(queueIdentifier)).second;
      //dPrint(20, "Added redirection to queue %u for %d:%d",
      //       queueIdentifier, majorTypeId, minorTypeId);
    }
  } else {
    result = (majorIter->second.queues.insert(queueIdentifier)).second;
    //dPrint(20, "Added redirection to queue %u for majortype %d",
    //       queueIdentifier, majorTypeId);
  }
  ownMutex_Unlock(redirectMutex);
  
  return result;
}

bool MultiData::removeRedirection(const unsigned int queueIdentifier,
                                  const int majorTypeId,
                                  const int minorTypeId /* = -1 */,
                                  const int senderId /* = -1 */)
{
  bool result = false;
  ownMutex_Lock(redirectMutex);
  t_redirectMapIterator majorIter = redirectInformation.find(majorTypeId);
  if (majorIter != redirectInformation.end()) {
    if (minorTypeId > -1) { // Minortype specified.
      t_minorRedirectMapIterator minorIter =
          majorIter->second.minorInformation.find(minorTypeId);
      if (minorIter != majorIter->second.minorInformation.end()) { // Minortype found.
        if (senderId > -1) { // Sender specified.
          t_senderInformationIterator senderIter =
              minorIter->second.senderInformation.find(senderId);
          if (senderIter != minorIter->second.senderInformation.end()) { // Given sender found.
            if (senderIter->second.queues.erase(queueIdentifier) > 0) {
              //dPrint(20, "Removed redirection to queue %u from sender 0x%08x "
              //"in type %d:%d", queueIdentifier, senderId, majorTypeId,
              //    minorTypeId);
              result = true;
            } else {
              /*     dPrint(ODWARN, "Could not find redirection to queue %u from sender "
                     "0x%08x in type %d:%d", queueIdentifier, senderId,
                     majorTypeId, minorTypeId);*/
              result = false;
            }
          } else { // Sender not found.
            dPrint(ODWARN, "Could not find sender 0x%08x in type %d:%d",
                   senderId, majorTypeId, minorTypeId);
            result = false;
          }
        } else { // Sender not specified.
          if (minorIter->second.queues.erase(queueIdentifier) > 0) {
            //dPrint(20, "Removed redirection to queue %u from type %d:%d",
            //       queueIdentifier, majorTypeId, minorTypeId);
            result = true;
          } else {
            dPrint(ODWARN, "Could not find redirection to queue %u in type %d:%d",
                   queueIdentifier, majorTypeId, minorTypeId);
            result = false;
          }
        }
      } else { // Minortype not found.
        dPrint(ODWARN, "Could not find type %d:%d (no redirections)", majorTypeId,
               minorTypeId);
        result = false;
      }
    } else { // Minortype not specified.
      if (majorIter->second.queues.erase(queueIdentifier) > 0) {
        //        dPrint(20, "Removed redirection to queue %u from majortype %d",
        //       queueIdentifier, majorTypeId);
        result = true;
      } else {
        dPrint(ODWARN, "Could not find redirection to queue %u in majortype %d",
               queueIdentifier, majorTypeId);
        result = false;
      }
    }
  } else { // Majortype not found.
    dPrint(ODWARN, "Could not find majortype %d (no redirections)", majorTypeId);
    result = false;
  }

  ownMutex_Unlock(redirectMutex);
  return result;
}

int MultiData::listRedirections(t_redirectionList &redirections,
                                const bool exactTypes /* = false */,
                                const int majorTypeId /* = -1 */,
                                const int minorTypeId /* = -1 */,
                                const int senderId /* = -1 */)
{
  int n = 0;
  ownMutex_Lock(redirectMutex);

  if (majorTypeId < 0) { // No types defined, loop through everything.
    if (!exactTypes) { // Loop only if exactTypes not defined.
      // Loop through majortypes.
      for (t_redirectMapConstIterator majorIter = redirectInformation.begin();
          majorIter != redirectInformation.end();
          ++majorIter)
      {
        n += writeQueueIdentifiersToRedirections(redirections,
            majorIter->second.queues, majorIter->first);
        
        n += writeMinorQueuesToRedirections(redirections,
                                            majorIter->second.minorInformation,
                                            exactTypes,
                                            majorIter->first,
                                            minorTypeId,
                                            senderId);
      }
    }
  } else { // Majortype defined.
   t_redirectMapConstIterator majorIter = redirectInformation.find(majorTypeId);
   if (majorIter != redirectInformation.end()) { // Majortype found.
     n += writeQueueIdentifiersToRedirections(redirections,
                                              majorIter->second.queues, majorIter->first);
        
     n += writeMinorQueuesToRedirections(redirections,
                                         majorIter->second.minorInformation,
                                         exactTypes,
                                         majorTypeId,
                                         minorTypeId,
                                         senderId);
   } else { // Majortype not found.
     //     dPrint(15, "Did not find majortype %d in redirections.", majorTypeId);
   }
  }
  
  ownMutex_Unlock(redirectMutex);
  return n;
}

int MultiData::listQueues(t_queueIdentifiers &queueList,
                          const int majorTypeId,
                          const int minorTypeId,
                          const int senderId)
{
  int n = 0;
  std::pair< t_queueIdentifiersIterator, bool > insertResultPair;
  
  ownMutex_Lock(redirectMutex);
  if (!redirectInformation.empty()) {
    // Find queues for this majortype.
    t_redirectMapConstIterator majIter = redirectInformation.find(majorTypeId);
    if (majIter != redirectInformation.end()) {
      if (!majIter->second.queues.empty()) {
        for (t_queueIdentifiersConstIterator qIter =
               majIter->second.queues.begin();
             qIter != majIter->second.queues.end();
             ++qIter)
        {
          insertResultPair = queueList.insert(*qIter);
          if (insertResultPair.second) {
            ++n;
            //dPrint(30, "Redirections for %d:%d 0x%08x, added queue %d from majortypes.",
            //     majorTypeId, minorTypeId, senderId, *qIter);
          } else {
            //dPrint(30, "Redirections for %d:%d 0x%08x, queue %d from "
            //    "majortypes, queue already listed.", majorTypeId, minorTypeId,
            //    senderId, *qIter);
          }
        }
      }
      // Find queues for this minortype.
      t_minorRedirectMapConstIterator minIter = majIter->second.minorInformation.find(minorTypeId);
      if (minIter != majIter->second.minorInformation.end()) {
        if (!minIter->second.queues.empty()) {
          for (t_queueIdentifiersConstIterator qIter =
                 minIter->second.queues.begin();
               qIter != minIter->second.queues.end();
               ++qIter)
          {
            insertResultPair = queueList.insert(*qIter);
            if (insertResultPair.second) {
              ++n;
              //              dPrint(30, "Redirections for %d:%d 0x%08x, added queue %d from minortypes.",
               //       majorTypeId, minorTypeId, senderId, *qIter);
            } else {
              dPrint(30, "Redirections for %d:%d 0x%08x, queue %d from "
                  "minortypes, queue already listed.", majorTypeId, minorTypeId,
                  senderId, *qIter);
            }
          }
        }
        // Find queues for this senderId
        t_senderInformationConstIterator sendIter = minIter->second.senderInformation.find(senderId);
        if (sendIter != minIter->second.senderInformation.end()) {
          if (!sendIter->second.queues.empty()) {
            for (t_queueIdentifiersConstIterator qIter =
                sendIter->second.queues.begin();
                qIter != sendIter->second.queues.end();
                ++qIter)
            {
              insertResultPair = queueList.insert(*qIter);
              if (insertResultPair.second) {
                ++n;
                 // dPrint(30, "Redirections for %d:%d 0x%08x, added queue %d from senders.",
                 //       majorTypeId, minorTypeId, senderId, *qIter);
              } else {
                 // dPrint(30, "Redirections for %d:%d 0x%08x, queue %d from "
                 //     "senders, queue already listed.", majorTypeId, minorTypeId,
                  //    senderId, *qIter);
              }
            }
          }
        }
      }
    } else {
       // dPrint(30, "Datatype %d:%d from 0x%08x not found.", majorTypeId,
       //        minorTypeId, senderId);
    }
  } else {
     // dPrint(30, "No multipliers."); 
  }
  ownMutex_Unlock(redirectMutex);
  return n;
}
  
int MultiData::renewRedirectionSenderIds()
{
  int notFound = 0;
  
  ownMutex_Lock(redirectMutex);
  if (!redirectInformation.empty()) {
    for (t_redirectMapIterator majorIter = redirectInformation.begin();
        majorIter != redirectInformation.end();
        ++majorIter)
    {
      for (t_minorRedirectMapIterator minorIter = majorIter->second.minorInformation.begin();
          minorIter != majorIter->second.minorInformation.end();
          ++minorIter)
      {
        // Mark each incorrect senderId here and update after looping through senderId:s
        t_clientIdUpdateList updateList;
        
        for (t_senderInformationIterator senderIter = minorIter->second.senderInformation.begin();
            senderIter != minorIter->second.senderInformation.end();
            ++senderIter)
        {
          uint32_t clientId;
          int result;
          result = connectionPtr->findClientId(clientId,
                                              senderIter->second.senderName);
          if (result == GIMI_OK) { // Client found.
            if (senderIter->first != clientId) { // Found id is different from one in here, mark for update.
              updateList.push_back(t_clientIdPair(senderIter->first, clientId));
            }
          } else if (result == GIMI_INVALIDCLIENT) { // Client not found.
             // dPrint(16, "Did not find new id for client '%s', will try updating later.",
             //       senderIter->second.senderName.c_str());
            ++notFound;
          } else { // Error
            dPrint(ODWARN, "Finding clientid failed. Aborting renewal.");
            return GIMI_ERROR;
          }
        }
        
        // Now update clientIds specified in the list.
        if (!updateList.empty()) {
          t_senderInformationIterator sendIt;
          for (t_clientIdUpdateListConstIterator iter = updateList.begin();
              iter != updateList.end();
              ++iter)
          {
            sendIt = minorIter->second.senderInformation.find(iter->first);
            if (sendIt == minorIter->second.senderInformation.end()) {
              dPrint(ODWARN, "Did not find clientId that was supposed to be updated. Abort.");
              return GIMI_ERROR; 
            } else {
               // dPrint(16, "Changing old id 0x%08x to 0x%08x for client '%s'",
               //       iter->first, iter->second, sendIt->second.senderName.c_str());
              SenderInfo copy = sendIt->second; // Take a copy.
              minorIter->second.senderInformation.erase(sendIt); // Erase entry with wrong id.
              minorIter->second.senderInformation.insert( 
                  t_senderInformationPair(iter->second, copy)); // Insert entry with new id.
            }
          }
        }
      }
    }
  }
  ownMutex_Unlock(redirectMutex);
  
  return notFound; 
}
  
bool MultiData::isEmpty() const
{
  return redirectInformation.empty();
}

} // end namespace gimi
